package com.albd.utils;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

public class ReflectUtil
{

	private static Map<Class<?>, ReflectUtil> classMap = new HashMap<Class<?>, ReflectUtil>();

	private Map<Class<?>[], Constructor<?>> constructorMap = new HashMap<Class<?>[], Constructor<?>>();

	private Map<String, Map<Type[], Method>> methodMap = new HashMap<String, Map<Type[], Method>>();

	private Map<String, Field> fieldMap = new HashMap<String, Field>();

	private Class<?> mClazz = null;

	private ReflectUtil(Class<?> clazz)
	{
		mClazz = clazz;
		fillConstructor(clazz.getConstructors());
		fillConstructor(clazz.getDeclaredConstructors());
		fillMethod(clazz.getMethods());
		fillMethod(clazz.getDeclaredMethods());
		fillField(clazz.getFields());
		fillField(clazz.getDeclaredFields());
	}

	private void fillConstructor(Constructor<?>[] constructors)
	{
		if(constructors == null)
			return;
		for (Constructor<?> constructor : constructors)
		{
			fillConstructor(constructor);
		}
	}

	private void fillConstructor(Constructor<?> constructor)
	{
		if(constructor == null)
			return;
		Class<?>[] parameterTypes = constructor.getParameterTypes();
		if(!constructorMap.containsKey(parameterTypes))
		{
			constructorMap.put(parameterTypes, constructor);
		}
	}

	private void fillMethod(Method[] methods)
	{
		if(methods == null)
			return;
		for (Method method : methods)
		{
			fillMethod(method);
		}
	}

	private void fillMethod(Method method)
	{
		if(method == null)
			return;
		String methodName = method.getName();
		Type[] parameterTypes = method.getGenericParameterTypes();
		Map<Type[], Method> methods = null;
		if(methodMap.containsKey(methodName))
		{
			methods = methodMap.get(methodName);
		}
		else
		{
			methods = new HashMap<Type[], Method>();
		}
		if(!methods.containsKey(parameterTypes))
		{
			methods.put(parameterTypes, method);
		}
		methodMap.put(methodName, methods);
	}

	private void fillField(Field[] fields)
	{
		if(fields == null)
			return;
		for (Field field : fields)
		{
			fillField(field);
		}
	}

	private void fillField(Field field)
	{
		if(field == null)
			return;
		String fieldName = field.getName();
		if(!fieldMap.containsKey(fieldName))
		{
			fieldMap.put(fieldName, field);
		}
	}

	private Constructor<?> findConstructor(Class<?>[] parameterTypes)
	{
		if(!constructorMap.containsKey(parameterTypes)) { return null; }
		Constructor<?> constructor = constructorMap.get(parameterTypes);
		if(constructor == null)
		{
			constructor = ReflectUtil.getConstructor(mClazz, parameterTypes);
			if(constructor != null)
			{
				fillConstructor(constructor);
			}
		}
		return constructor;
	}

	private Method findMethod(String methodName, Class<?>[] parameterTypes)
	{
		if(!methodMap.containsKey(methodName)) { return null; }
		Map<Type[], Method> methods = methodMap.get(methodName);
		if(!methods.containsKey(parameterTypes)) { return null; }
		Method method = methods.get(parameterTypes);
		if(method == null)
		{
			method = ReflectUtil.getMethod(mClazz, methodName, parameterTypes);
			if(method != null)
			{
				fillMethod(method);
			}
		}
		return method;
	}

	private Field findField(String fieldName)
	{
		if(!fieldMap.containsKey(fieldName)) { return null; }
		Field field = fieldMap.get(fieldName);
		if(field == null)
		{
			field = ReflectUtil.getField(mClazz, fieldName);
			if(field != null)
			{
				fillField(field);
			}
		}
		return field;
	}

	public Object newInstance(Class<?>[] clazzs, Object[] args)
	{
		try
		{
			return newInstanceThrow(clazzs, args);
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	public Object invoke(Object obj, String methodName, Class<?>[] parameterTypes, Object[] args)
	{
		try
		{
			return invokeThrow(obj, methodName, parameterTypes, args);
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	public Object getValue(Object obj, String fieldName)
	{
		try
		{
			return getValueThrow(obj, fieldName);
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	public void setValue(Object obj, String fieldName, Object value)
	{
		try
		{
			setValueThrow(obj, fieldName, value);
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	public Object newInstanceThrow(Class<?>[] clazzs, Object[] args) throws IllegalArgumentException,
					InstantiationException, IllegalAccessException, InvocationTargetException
	{
		Constructor<?> constructor = findConstructor(clazzs);
		Object newInstanceObject = ReflectUtil.newInstanceThrow(constructor, args);
		return newInstanceObject;
	}

	public Object invokeThrow(Object obj, String methodName, Class<?>[] parameterTypes, Object[] args)
					throws IllegalArgumentException, IllegalAccessException, InvocationTargetException
	{
		if(methodName == null) { throw new RuntimeException(new IllegalArgumentException("methodName == null")); }
		Method method = findMethod(methodName, parameterTypes);
		Object newInstanceObject = ReflectUtil.invokeThrow(method, obj, args);
		return newInstanceObject;
	}

	public Object getValueThrow(Object obj, String fieldName) throws IllegalArgumentException, IllegalAccessException
	{
		if(fieldName == null) { throw new RuntimeException(new IllegalArgumentException("fieldName == null")); }
		Field field = findField(fieldName);
		Object fieldValue = ReflectUtil.getValueThrow(field, obj);
		return fieldValue;
	}

	public void setValueThrow(Object obj, String fieldName, Object value) throws IllegalArgumentException,
					IllegalAccessException
	{
		if(fieldName == null) { throw new RuntimeException(new IllegalArgumentException("fieldName == null")); }
		Field field = findField(fieldName);
		ReflectUtil.setValueThrow(field, obj, value);
	}

	public static ReflectUtil createReflectUtil(Class<?> clazz)
	{
		if(clazz == null) { throw new RuntimeException(new IllegalArgumentException("clazz == null")); }
		ReflectUtil reflectUtil = null;
		if(classMap.containsKey(clazz))
		{
			reflectUtil = classMap.get(clazz);
		}
		else
		{
			reflectUtil = new ReflectUtil(clazz);
			classMap.put(clazz, reflectUtil);
		}
		return reflectUtil;
	}

	public static Object newInstance(Class<?> clazz, Class<?>[] argsClass, Object[] args)
	{
		try
		{
			return newInstanceThrow(clazz, argsClass, args);
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	public static Object invoke(Class<?> clazz, Object obj, String methodName, Class<?>[] parameterTypes, Object[] args)
	{
		try
		{
			return invokeThrow(clazz, obj, methodName, parameterTypes, args);
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	public static Object getValue(Class<?> clazz, Object obj, String fieldName)
	{
		try
		{
			return getValueThrow(clazz, obj, fieldName);
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	public static void setValue(Class<?> clazz, Object obj, String fieldName, Object value)
	{
		try
		{
			setValueThrow(clazz, obj, fieldName, value);
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	public static Object newInstanceThrow(Class<?> clazz, Class<?>[] argsClass, Object[] args)
					throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException,
					IllegalAccessException, InvocationTargetException
	{
		if(clazz == null) { throw new IllegalArgumentException("clazz is null"); }
		if(!checkClassAndArgs(argsClass, args)) { throw new IllegalArgumentException("argsClass.length != args.length"); }
		Constructor<?> constructor = getConstructorThrow(clazz, argsClass);
		Object newInstanceObject = newInstanceThrow(constructor, args);
		return newInstanceObject;
	}

	public static Object invokeThrow(Class<?> clazz, Object obj, String methodName, Class<?>[] parameterTypes,
					Object[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException,
					IllegalAccessException, InvocationTargetException
	{
		if(clazz == null || methodName == null) { throw new IllegalArgumentException("clazz, obj, methodName is null"); }
		if(!checkClassAndArgs(parameterTypes, args)) { throw new IllegalArgumentException(
						"parameterTypes.length != args.length"); }
		Method method = getMethodThrow(clazz, methodName, parameterTypes);
		Object methodReturnValue = invokeThrow(method, obj, args);
		return methodReturnValue;
	}

	public static Object getValueThrow(Class<?> clazz, Object obj, String fieldName) throws SecurityException,
					NoSuchFieldException, IllegalArgumentException, IllegalAccessException
	{
		if(clazz == null || fieldName == null) { throw new IllegalArgumentException("clazz or value is null"); }
		Field field = getFieldThrow(clazz, fieldName);
		Object fieldValue = getValueThrow(field, obj);
		return fieldValue;
	}

	public static void setValueThrow(Class<?> clazz, Object obj, String fieldName, Object value)
					throws IllegalArgumentException, IllegalAccessException, SecurityException, NoSuchFieldException
	{
		if(clazz == null || fieldName == null) { throw new IllegalArgumentException("clazz or value is null"); }
		Field field = getFieldThrow(clazz, fieldName);
		setValueThrow(field, obj, value);
	}

	public static Constructor<?> getConstructor(Class<?> clazz, Class<?>[] argsClass)
	{
		try
		{
			return getConstructorThrow(clazz, argsClass);
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	public static Method getMethod(Class<?> clazz, String methodName, Class<?>[] parameterTypes)
	{
		try
		{
			return getMethodThrow(clazz, methodName, parameterTypes);
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	public static Field getField(Class<?> clazz, String fieldName)
	{
		try
		{
			return getFieldThrow(clazz, fieldName);
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	public static Object newInstance(Constructor<?> constructor, Object[] args)
	{
		try
		{
			return newInstanceThrow(constructor, args);
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	public static Object invoke(Method method, Object obj, Object[] args)
	{
		try
		{
			return invokeThrow(method, obj, args);
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	public static Object getValue(Field field, Object obj)
	{
		try
		{
			return getValueThrow(field, obj);
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	public static void setValue(Field field, Object obj, Object value)
	{
		try
		{
			setValueThrow(field, obj, value);
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	public static Constructor<?> getConstructorThrow(Class<?> clazz, Class<?>[] argsClass) throws SecurityException,
					NoSuchMethodException
	{
		Constructor<?> constructor = null;
		try
		{
			constructor = clazz.getConstructor(argsClass);
		}
		catch (Exception e)
		{}
		if(constructor == null)
		{
			constructor = clazz.getDeclaredConstructor(argsClass);
		}
		return constructor;
	}

	public static Method getMethodThrow(Class<?> clazz, String methodName, Class<?>[] parameterTypes)
					throws SecurityException, NoSuchMethodException
	{
		Method method = null;
		try
		{
			method = clazz.getMethod(methodName, parameterTypes);
		}
		catch (Exception e)
		{}
		if(method == null)
		{
			method = clazz.getDeclaredMethod(methodName, parameterTypes);
		}
		return method;
	}

	public static Field getFieldThrow(Class<?> clazz, String fieldName) throws SecurityException, NoSuchFieldException
	{
		Field field = null;
		try
		{
			field = clazz.getField(fieldName);
		}
		catch (Exception e)
		{}
		if(field == null)
		{
			field = clazz.getDeclaredField(fieldName);
		}
		return field;
	}

	public static Object newInstanceThrow(Constructor<?> constructor, Object[] args) throws IllegalArgumentException,
					InstantiationException, IllegalAccessException, InvocationTargetException
	{
		if(constructor == null) { throw new IllegalArgumentException("constructor is null"); }
		if(!checkClassAndArgs(constructor.getParameterTypes(), args)) { throw new IllegalArgumentException(
						"constructor.getParameterTypes().length != args.length"); }
		Object newInstanceObject = null;
		boolean isPublic = Modifier.isPublic(constructor.getModifiers());

		if(!isPublic)
		{
			constructor.setAccessible(true);
		}
		newInstanceObject = constructor.newInstance(args);
		if(!isPublic)
		{
			constructor.setAccessible(false);
		}
		return newInstanceObject;
	}

	public static Object invokeThrow(Method method, Object obj, Object[] args) throws IllegalArgumentException,
					IllegalAccessException, InvocationTargetException
	{
		if(method == null) { throw new IllegalArgumentException("method == null"); }
		if(!checkClassAndArgs(method.getParameterTypes(), args)) { throw new IllegalArgumentException(
						"method.getParameterTypes().length != args.length"); }
		Object methodReturnValue = null;
		boolean isPublic = Modifier.isPublic(method.getModifiers());
		if(!isPublic)
		{
			method.setAccessible(true);
		}
		methodReturnValue = method.invoke(obj, args);
		if(!isPublic)
		{
			method.setAccessible(false);
		}
		return methodReturnValue;
	}

	public static Object getValueThrow(Field field, Object obj) throws IllegalArgumentException, IllegalAccessException
	{
		if(field == null) { throw new IllegalArgumentException("field == null"); }
		Object fieldValue = null;
		boolean isPublic = Modifier.isPublic(field.getModifiers());
		if(!isPublic)
		{
			field.setAccessible(true);
		}
		fieldValue = field.get(obj);
		if(!isPublic)
		{
			field.setAccessible(false);
		}
		return fieldValue;
	}

	public static void setValueThrow(Field field, Object obj, Object value) throws IllegalArgumentException,
					IllegalAccessException
	{
		if(field == null) { throw new IllegalArgumentException("field == null"); }

		boolean isPublic = Modifier.isPublic(field.getModifiers());
		if(!isPublic)
		{
			field.setAccessible(true);
		}
		field.set(obj, value);
		if(!isPublic)
		{
			field.setAccessible(false);
		}
	}

	private static boolean checkClassAndArgs(Class<?>[] argsClass, Object[] args)
	{
		int argsClassLength = 0;
		int argsLength = 0;

		if(argsClass != null)
		{
			argsClassLength = argsClass.length;
		}
		if(args != null)
		{
			argsLength = args.length;
		}
		return argsClassLength == argsLength;
	}

}
